/*
 * Copyright (c) 2014, Mentor Graphics Corporation
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <errno.h>
#include <metal/mutex.h>
#include <metal/spinlock.h>
#include <metal/utilities.h>
#include <openamp/open_amp.h>
#include <openamp/rpmsg_retarget.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>

/*************************************************************************
 *	Description
 *	This files contains rpmsg based redefinitions for C RTL system calls
 *	such as _open, _read, _write, _close.
 *************************************************************************/
static struct rpmsg_rpc_data *rpmsg_default_rpc;

static int rpmsg_rpc_ept_cb(struct rpmsg_endpoint *ept, void *data, size_t len,
                uint32_t src, void *priv)
{
    struct rpmsg_rpc_syscall *syscall;

    (void)priv;
    (void)src;

    if (data && ept) {
        syscall = data;
        if (syscall->id == TERM_SYSCALL_ID) {
            rpmsg_destroy_ept(ept);
        } else {
            struct rpmsg_rpc_data *rpc;

            rpc = metal_container_of(ept,
                         struct rpmsg_rpc_data,
                         ept);
            metal_spinlock_acquire(&rpc->buflock);
            if (rpc->respbuf && rpc->respbuf_len != 0) {
                if (len > rpc->respbuf_len)
                    len = rpc->respbuf_len;
                memcpy(rpc->respbuf, data, len);
            }
            atomic_flag_clear(&rpc->nacked);
            metal_spinlock_release(&rpc->buflock);
        }
    }

    return RPMSG_SUCCESS;
}

static void rpmsg_service_unbind(struct rpmsg_endpoint *ept)
{
    struct rpmsg_rpc_data *rpc;

    rpc = metal_container_of(ept, struct rpmsg_rpc_data, ept);
    rpc->ept_destroyed = 1;
    rpmsg_destroy_ept(ept);
    atomic_flag_clear(&rpc->nacked);
    if (rpc->shutdown_cb)
        rpc->shutdown_cb(rpc);
}

int rpmsg_rpc_init(struct rpmsg_rpc_data *rpc,
           struct rpmsg_device *rdev,
           const char *ept_name, uint32_t ept_addr,
           uint32_t ept_raddr,
           void *poll_arg, rpmsg_rpc_poll poll,
           rpmsg_rpc_shutdown_cb shutdown_cb)
{
    int ret;

    if (!rpc || !rdev)
        return -EINVAL;
    metal_spinlock_init(&rpc->buflock);
    metal_mutex_init(&rpc->lock);
    rpc->shutdown_cb = shutdown_cb;
    rpc->poll_arg = poll_arg;
    rpc->poll = poll;
    rpc->ept_destroyed = 0;
    rpc->respbuf = NULL;
    rpc->respbuf_len = 0;
    rpc->nacked = (atomic_flag)ATOMIC_FLAG_INIT;
    atomic_flag_test_and_set(&rpc->nacked);
    ret = rpmsg_create_ept(&rpc->ept, rdev,
                   ept_name, ept_addr, ept_raddr,
                   rpmsg_rpc_ept_cb, rpmsg_service_unbind);
    if (ret != 0) {
        metal_mutex_release(&rpc->lock);
        return -EINVAL;
    }
    while (!is_rpmsg_ept_ready(&rpc->ept)) {
        if (rpc->poll)
            rpc->poll(rpc->poll_arg);
    }
    return 0;
}

void rpmsg_rpc_release(struct rpmsg_rpc_data *rpc)
{
    if (!rpc)
        return;
    if (rpc->ept_destroyed == 0)
        rpmsg_destroy_ept(&rpc->ept);
    metal_mutex_acquire(&rpc->lock);
    metal_spinlock_acquire(&rpc->buflock);
    rpc->respbuf = NULL;
    rpc->respbuf_len = 0;
    metal_spinlock_release(&rpc->buflock);
    metal_mutex_release(&rpc->lock);
    metal_mutex_deinit(&rpc->lock);
}

int rpmsg_rpc_send(struct rpmsg_rpc_data *rpc,
           void *req, size_t len,
           void *resp, size_t resp_len)
{
    int ret;

    if (!rpc)
        return -EINVAL;
    metal_spinlock_acquire(&rpc->buflock);
    rpc->respbuf = resp;
    rpc->respbuf_len = resp_len;
    metal_spinlock_release(&rpc->buflock);
    (void)atomic_flag_test_and_set(&rpc->nacked);
    ret = rpmsg_send(&rpc->ept, req, len);
    if (ret < 0)
        return -EINVAL;
    if (!resp)
        return ret;
    while ((atomic_flag_test_and_set(&rpc->nacked))) {
        if (rpc->poll)
            rpc->poll(rpc->poll_arg);
    }
    return ret;
}

void rpmsg_set_default_rpc(struct rpmsg_rpc_data *rpc)
{
    if (!rpc)
        return;
    rpmsg_default_rpc = rpc;
}

/*************************************************************************
 *
 *   FUNCTION
 *
 *       _open
 *
 *   DESCRIPTION
 *
 *       Open a file.  Minimal implementation
 *
 *************************************************************************/
#define MAX_BUF_LEN 496UL

int _open(const char *filename, int flags, int mode)
{
    struct rpmsg_rpc_data *rpc = rpmsg_default_rpc;
    struct rpmsg_rpc_syscall *syscall;
    struct rpmsg_rpc_syscall resp;
    int filename_len = strlen(filename) + 1;
    unsigned int payload_size = sizeof(*syscall) + filename_len;
    unsigned char tmpbuf[MAX_BUF_LEN];
    int ret;

    if (!filename || payload_size > (int)MAX_BUF_LEN) {
        return -EINVAL;
    }

    if (!rpc)
        return -EINVAL;

    /* Construct rpc payload */
    syscall = (void *)tmpbuf;
    syscall->id = OPEN_SYSCALL_ID;
    syscall->args.int_field1 = flags;
    syscall->args.int_field2 = mode;
    syscall->args.data_len = filename_len;
    memcpy(tmpbuf + sizeof(*syscall), filename, filename_len);

    resp.id = 0;
    ret = rpmsg_rpc_send(rpc, tmpbuf, payload_size,
                 (void *)&resp, sizeof(resp));
    if (ret >= 0) {
        /* Obtain return args and return to caller */
        if (resp.id == OPEN_SYSCALL_ID)
            ret = resp.args.int_field1;
        else
            ret = -EINVAL;
    }

    return ret;
}

/*************************************************************************
 *
 *   FUNCTION
 *
 *       _read
 *
 *   DESCRIPTION
 *
 *       Low level function to redirect IO to serial.
 *
 *************************************************************************/
int _read(int fd, char *buffer, int buflen)
{
    struct rpmsg_rpc_syscall syscall;
    struct rpmsg_rpc_syscall *resp;
    struct rpmsg_rpc_data *rpc = rpmsg_default_rpc;
    int payload_size = sizeof(syscall);
    unsigned char tmpbuf[MAX_BUF_LEN];
    int ret;

    if (!rpc || !buffer || buflen == 0)
        return -EINVAL;

    /* Construct rpc payload */
    syscall.id = READ_SYSCALL_ID;
    syscall.args.int_field1 = fd;
    syscall.args.int_field2 = buflen;
    syscall.args.data_len = 0;	/*not used */

    resp = (void *)tmpbuf;
    resp->id = 0;
    ret = rpmsg_rpc_send(rpc, (void *)&syscall, payload_size,
                 tmpbuf, sizeof(tmpbuf));

    /* Obtain return args and return to caller */
    if (ret >= 0) {
        if (resp->id == READ_SYSCALL_ID) {
            if (resp->args.int_field1 > 0) {
                int tmplen = resp->args.data_len;
                unsigned char *tmpptr = tmpbuf;

                tmpptr += sizeof(*resp);
                if (tmplen > buflen)
                    tmplen = buflen;
                memcpy(buffer, tmpptr, tmplen);
            }
            ret = resp->args.int_field1;
        } else {
            ret = -EINVAL;
        }
    }

    return ret;
}

/*************************************************************************
 *
 *   FUNCTION
 *
 *       _write
 *
 *   DESCRIPTION
 *
 *       Low level function to redirect IO to serial.
 *
 *************************************************************************/
int _write(int fd, const char *ptr, int len)
{
    int ret;
    struct rpmsg_rpc_syscall *syscall;
    struct rpmsg_rpc_syscall resp;
    int payload_size = sizeof(*syscall) + len;
    struct rpmsg_rpc_data *rpc = rpmsg_default_rpc;
    unsigned char tmpbuf[MAX_BUF_LEN];
    unsigned char *tmpptr;
    int null_term = 0;

    if (!rpc)
        return -EINVAL;
    if (fd == 1)
        null_term = 1;

    syscall = (void *)tmpbuf;
    syscall->id = WRITE_SYSCALL_ID;
    syscall->args.int_field1 = fd;
    syscall->args.int_field2 = len;
    syscall->args.data_len = len + null_term;
    tmpptr = tmpbuf + sizeof(*syscall);
    memcpy(tmpptr, ptr, len);
    if (null_term == 1) {
        *(char *)(tmpptr + len + null_term) = 0;
        payload_size += 1;
    }
    resp.id = 0;
    ret = rpmsg_rpc_send(rpc, tmpbuf, payload_size,
                 (void *)&resp, sizeof(resp));

    if (ret >= 0) {
        if (resp.id == WRITE_SYSCALL_ID)
            ret = resp.args.int_field1;
        else
            ret = -EINVAL;
    }

    return ret;

}

/*************************************************************************
 *
 *   FUNCTION
 *
 *       _close
 *
 *   DESCRIPTION
 *
 *       Close a file.  Minimal implementation
 *
 *************************************************************************/
int _close(int fd)
{
    int ret;
    struct rpmsg_rpc_syscall syscall;
    struct rpmsg_rpc_syscall resp;
    int payload_size = sizeof(syscall);
    struct rpmsg_rpc_data *rpc = rpmsg_default_rpc;

    if (!rpc)
        return -EINVAL;
    syscall.id = CLOSE_SYSCALL_ID;
    syscall.args.int_field1 = fd;
    syscall.args.int_field2 = 0;	/*not used */
    syscall.args.data_len = 0;	/*not used */

    resp.id = 0;
    ret = rpmsg_rpc_send(rpc, (void *)&syscall, payload_size,
                 (void *)&resp, sizeof(resp));

    if (ret >= 0) {
        if (resp.id == CLOSE_SYSCALL_ID)
            ret = resp.args.int_field1;
        else
            ret = -EINVAL;
    }

    return ret;
}
